var fs = require('fs'),
  util = require('util'),
  http = require('http'),
  https = require('https');
  // union = require('union'),
  ecstatic = require('ecstatic'),
  path = require('path'),
  url = require("url"),
  nodeCgiProcessor = require("../modules/node-cgi"),
  apiCommonConfig = require('../api/common/config')
  express = require('express'),
  compression = require('compression'),
  socketIoCreator = require('../modules/socketIo'),
  websocketCreator = require('../modules/websocket'),
  proxy = require('http-proxy-middleware'),
  _ = require('underscore');

function matchContext(requestPath, clazz, mapping) {
  if ((typeof mapping === "string" && requestPath.indexOf(mapping) === 0) || (_.isRegExp(mapping) && mapping.test(requestPath))) {
    /**
     * cite: http://www.tuicool.com/articles/RVrm6z
     */
    // delete require.cache[path.resolve('.', clazz)];
    try {
      var servlet = require(path.resolve(__dirname, '../', clazz), require);
      if (!servlet)
        return null;
      else if (servlet[requestPath])
        return servlet[requestPath];
      else {
        for (var param in servlet) {
          if (requestPath.indexOf(param) === 0) {
            return servlet[param];
          }
        }
      }
    } catch (e) {
      throw e;
    }
  }
}

function loadContext(requestPath, clazz, mapping) {
  if (_.isArray(mapping)) {
    for (var i = 0; i < mapping.length; i++) {
      var matchedContext = matchContext(requestPath, clazz, mapping[i]);
      if (matchedContext)
        return matchedContext;
    }
  } else {
    return matchContext(requestPath, clazz, mapping);
  }
}

function loadWSContext(clazz, mapping) {
  var wsContexts = [];

  try {
    var wsConfig = require(path.resolve(__dirname, '../', clazz));

    if (_.isArray(mapping)) {
      for (var i = 0; i < mapping.length; i++) {
        setWSContexts(mapping[i], wsConfig[mapping[i]]); //mapping和key要精确匹配
      }
    } else {
      setWSContexts(mapping, wsConfig[mapping]);
    }

    return wsContexts;

  } catch (e) {
    throw e;
  }

  function setWSContexts(mapping, wsOption) {
    if (_.isFunction(wsOption)) {
      wsContexts.push(_.extend({ path: mapping }, wsOption(mapping)));
    } else if (_.isObject(wsOption)) {
      wsContexts.push(_.extend({ path: mapping }, wsOption));
    }
  }
}

var HTTPServer = exports.HTTPServer = function(options, log) {
  options = options || {};

  if (options.root) {
    options.root = options.root;
  } else {
    try {
      fs.lstatSync('./public');
      options.root = './public';
    } catch (err) {
      options.root = './';
    }
  }

  if (options.headers) {
    options.headers = options.headers;
  }

  options.cache = options.cache || 3600; // in seconds.
  options.showDir = options.showDir !== 'false';
  options.autoIndex = options.autoIndex !== 'false';

  if (options.ext) {
    options.ext = options.ext === true ? 'html' : options.ext;
  }

  var app = express();
  app.use(compression());

  // log filter
  app.use(function(req, res, next) {
    options.logFn && options.logFn(req, res);
    next();
  });

  var config = require('../conf/ns.conf.js');

  if (config.ws && !apiCommonConfig.USE_MOCK) {
    var aloadedWSContexts = [];

    if (_.isArray(config.ws)) {
      for (var i = 0; i < config.ws.length; i++) {
        var loadedWSContexts = loadWSContext(config.ws[i].clazz, config.ws[i].mapping);
        if (loadedWSContexts) {
          aloadedWSContexts.push(loadedWSContexts);
        }
      }

    } else {
      var loadedWSContexts = loadWSContext(config.ws.clazz, config.ws.mapping);
      if (loadedWSContexts) {
        aloadedWSContexts.push(loadedWSContexts);
      }
    }

    var self = this;
    self._websocketUpgrade = {}

    aloadedWSContexts.map(function(loadedWSContexts) {
      loadedWSContexts.map(function(loadedWSContext) {
        var proxyOption = {
          ws: true,
          secure: false, //ssl时必须
          changeOrigin: true //ssl时必须
        }

        if (loadedWSContext.headers) {
          proxyOption.headers = loadedWSContext.headers;
        }

        if (loadedWSContext.pathRewrite) {
          proxyOption.pathRewrite = {};
          proxyOption.pathRewrite[loadedWSContext.path] = loadedWSContext.pathRewrite;
        }
        var wsProxy = proxy(loadedWSContext.target, proxyOption);
        app.use(loadedWSContext.path, wsProxy);
        self._websocketUpgrade[loadedWSContext.path] = wsProxy.upgrade;
      })
    })
  }

  if (config.filter) {
    var filter = config.filter
    app.use(function(req, res, next) {
      res.setHeader("Content-Type", 'text/html');
      if (_.isArray(filter)) {
        var flag = false;
        for (var i = 0; i < filter.length; i++) {
          var loadedContext = loadContext(url.parse(req.url).path, filter[i].clazz, filter[i].mapping);
          if (loadedContext) {
            loadedContext(req, res, next, filter[i], options);
            flag = true;
            break;
          }
        }
        if (flag === false) {
          next();
        }
      } else {
        var loadedContext = loadContext(url.parse(req.url).path, filter.clazz, filter.mapping);
        if (loadedContext) {
          loadedContext(req, res, next, filter, options);
        } else {
          next();
        }
      }
    });
  }

  if (config.controller) {
    var controller = config.controller
    app.use(function(req, res, next) {
      res.setHeader("Content-Type", 'text/html');
      if (_.isArray(controller)) {
        var flag = false;
        for (var i = 0; i < controller.length; i++) {
          var loadedContext = loadContext(url.parse(req.url).path, controller[i].clazz, controller[i].mapping);
          if (loadedContext) {
            loadedContext(req, res, next, controller[i], options);
            flag = true;
            break;
          }
        }
        if (flag === false) {
          next();
        }
      } else {
        var loadedContext = loadContext(url.parse(req.url).path, controller.clazz, controller.mapping);
        if (loadedContext) {
          loadedContext(req, res, next, controller, options);
        } else {
          next();
        }
      }
    });
  }

  app.use(function(req, res, next) {
    var pathname = url.parse(req.url).pathname; // "/" "/foo"
    if (options.cgiPath && nodeCgiProcessor && pathname.indexOf("/cgi-bin/") === 0) {
      var filename = path.resolve(path.resolve(options.cgiPath) + pathname.replace("/cgi-bin", ""));
      if (path.extname(pathname) === ".js") {
        try {
          nodeCgiProcessor.processFile(filename, {
            req: req,
            res: res
          });
        } catch (e) {
          res.end(e.message);
        }
      } else if (path.extname(pathname) === ".nd") {
        try {
          var httpResponse = nodeCgiProcessor.processFile(filename, {
            req: req,
            res: res
          });
          var headers = httpResponse.headers();
          // for (var key in headers) {
          //   res.setHeader(key, headers[key]);
          // }
          res.set(headers);
          if (!headers.hasSelfEnd) {
            res.end(httpResponse.content());
          }
        } catch (e) {
          res.end(e.message);
        }
      } else {
        next();
      }
    } else {
      next();
    }
  });

  app.use(ecstatic({
    root: options.root,
    cache: options.cache,
    showDir: options.showDir,
    autoIndex: options.autoIndex,
    defaultExt: options.ext
  }));

  var server = null;

  if (options.ssl) {
    var privateKey  = fs.readFileSync('conf/private.pem', 'utf8');
    var certificate = fs.readFileSync('conf/file.crt', 'utf8');
    var credentials = { key: privateKey, cert: certificate };

    server = https.createServer(credentials, app);
  } else {
    server = http.createServer(app);
  }

  if (apiCommonConfig.USE_MOCK || apiCommonConfig.USE_LOCAL_WS_PATH) {
    if (config.ws && config.ws[0]) {
      const wsPath = config.ws[0].mapping[0];
      if (apiCommonConfig.WS_ENGINE === 'socketIo') {
        socketIoCreator(server, wsPath);
      } else {
        websocketCreator(server, wsPath);
      }
    }
  } else {
    if (apiCommonConfig.WS_ENGINE === 'websocket') {
      for (var path in this._websocketUpgrade) {
        console.log('bind upgrade', path)
        server.on('upgrade', this._websocketUpgrade[path]);
        break;
      }
    }
  }
  
  this.server = server;
};

HTTPServer.prototype.listen = function() {
  return this.server.listen.apply(this.server, arguments);
};

HTTPServer.prototype.close = function() {
  return this.server.close();
};

exports.createServer = function(options, log) {
  return new HTTPServer(options, log);
};
